package cloudjunit;

import java.beans.XMLEncoder;
import java.io.*;
import java.util.*;
import java.util.regex.Pattern;

import dynamojunit.DynamoDbCommon;
import dynamojunit.TestClassTable;
import dynamojunit.WorkflowTable;

import Common.*;
import JarJunit.*;


public class UnitTestActivitiesImpl implements UnitTestActivities
{
	static final String LOCAL_WORK_DIR = "."+File.separator+"files"+File.separator;
	static final String TEST_OUTPUT_FILE_NAME_PREFIX = "TestResult_";
	
	private TestClassTable testClassTable;
	private WorkflowTable workFlowTable;
	
	public UnitTestActivitiesImpl()
	{
		super();
		this.testClassTable = new TestClassTable(true);
		this.workFlowTable = new WorkflowTable(true);
	}
	
	@Override
	public List<String> findUnitTestsInJar(String workFlowId, String jarFileRemotePath) throws Exception
    {
		TraceWriter.trace("findUnitTestsInJar: STARTED");
		cleanOldJarsRoutine();
		
		TraceWriter.trace("Retreiving jar: " + jarFileRemotePath + " ...");
		
		List<String> unitTestsList = new ArrayList<String>();
		workFlowTable.setWorkFlowState(workFlowId, new JunitWorkFlowState(JunitWorkFlowState.ANALYZING));
		
		downloadJarToLocalDriveIfNotThere(Config.data.defaultS3Bucket, jarFileRemotePath);
		
		int totalNumberOfTestsInJar = JarJunitTestsParser.get_test_classes_from_jar(getJarLocalFullPath(jarFileRemotePath), unitTestsList);
		
		updateWorkFlowTableWithTests(workFlowId, totalNumberOfTestsInJar, unitTestsList);
		workFlowTable.setWorkFlowState(workFlowId, new JunitWorkFlowState(JunitWorkFlowState.RUNNING));
		
		TraceWriter.trace("findUnitTestsInJar: DONE");
		return unitTestsList;
    }
	
	private void updateWorkFlowTableWithTests(String workFlowId, int totalNumberOfTestsInJar, List<String> unitTestsList)
	{
		workFlowTable.updateTotalNumberOfTests(workFlowId, totalNumberOfTestsInJar);
		workFlowTable.updateTestClasses(workFlowId, unitTestsList.toArray(new String[unitTestsList.size()]));
	}

	@Override
	public String runUnitTestAndUploadResults(String workFlowId, String jarFileRemotePath, String ResultsFolderRemotePath, String unitTestClassName) throws Exception
	{
		TraceWriter.trace("runUnitTestAndUploadResults STARTED");
		cleanOldJarsRoutine();
		
		TraceWriter.trace("========================================================");
		TraceWriter.trace("running S3 file "+jarFileRemotePath);
		TraceWriter.trace("writing results to "+ResultsFolderRemotePath);
		TraceWriter.trace("running class "+unitTestClassName);
		TraceWriter.trace("========================================================");
		
		String tcid = DynamoDbCommon.convertTestClassToTCID(unitTestClassName, workFlowId);
		testClassTable.startTestClassEntry(tcid, unitTestClassName);
		
		downloadJarToLocalDriveIfNotThere(Config.data.defaultS3Bucket, jarFileRemotePath);
	
		//run tests
		JarJunitResult testResult = JarJunitTestRunner.run_tests_from_class_in_jar(getJarLocalFullPath(jarFileRemotePath), unitTestClassName);
		
		List<String> listOfAllMethods = JarJunitTestsParser.get_test_methods_from_jar(getJarLocalFullPath(jarFileRemotePath), unitTestClassName);

		String resultsFileRemotePath = uploadTestResultToS3(workFlowId, testResult, jarFileRemotePath, ResultsFolderRemotePath, unitTestClassName);
		
		updateTestResultInTestsTable(workFlowId,testResult,unitTestClassName,resultsFileRemotePath,listOfAllMethods);
			
		TraceWriter.trace( "runUnitTestAndUploadResults DONE" );
		return "DONE";
	}
	
	@Override
	public void initUnitTestWorkFlowData(String workFlowId)
	{
		TraceWriter.trace("initUnitTestWorkFlowData called!");
		workFlowTable.startWorkflowEntry(workFlowId);
	}
	
	@Override
	public void finalizeWorkFlowData(String workFlowId)
	{
		TraceWriter.trace("finalizeWorkFlowData called!");
		workFlowTable.updateFinalResultAndFinish(workFlowId, WorkflowTable.FINAL_RES_ALLOK);
	}
	
	////////////////////////////////////////////
	// Utilities Functions
	
	private void cleanOldJarsRoutine()
	{
		final Date now = new Date();
		final long expiryTimeMs = ConfigParams.JAR_FILES_EXPIRY_TIME;
		
		TraceWriter.trace("cleanOldJarsRoutine started.");

		File dir = new File(LOCAL_WORK_DIR);
		
		if (!dir.exists())
			return;
		
		File[] files = dir.listFiles();
		
		FileFilter fileFilter = new FileFilter() {
		    public boolean accept(File file) {
		    	long expiryTime = file.lastModified() + expiryTimeMs;
		    	boolean isExpired = now.getTime() > expiryTime;
		    	boolean isRelevantExt = file.getName().endsWith(".jar") || file.getName().endsWith(".xml");
		        return !file.isDirectory() && isRelevantExt && isExpired;
		    }
		};
		files = dir.listFiles(fileFilter);
		
		// Delete old jars according to the above filter
		for (File file : files)
		{
			try
			{
				TraceWriter.trace("cleanOldJarsRoutine: deletes file: " + file.getName());
				file.delete();
			}
			catch(Exception e)
			{
				TraceWriter.trace("cleanOldJarsRoutine: ERROR deleting file: " + file.getName());
				e.printStackTrace();
			}
		}
	}
	
	private void updateTestResultInTestsTable(String workFlowId, JarJunitResult testResult, String unitTestClassName, String resultsFileRemotePath, List<String> listOfAllClassMethods)
	{
		String tcid = DynamoDbCommon.convertTestClassToTCID(unitTestClassName, workFlowId);
		
		List<String> list_of_all_failed_methods = new ArrayList<String>();
		List<String> list_of_all_passed_methods = new ArrayList<String>();
		JarJunitTestsParser.categorize_methods(listOfAllClassMethods, testResult, list_of_all_passed_methods, list_of_all_failed_methods);
		
		testClassTable.updateTestClassResultAndFinish(tcid, testResult.getPassedNumOfTests(), testResult.getFailedNumOfTests(), resultsFileRemotePath, list_of_all_failed_methods, list_of_all_passed_methods, testResult.getRuntime());
		workFlowTable.updateNumericTestResults(workFlowId, testResult.getPassedNumOfTests(), testResult.getFailedNumOfTests());
	}
	
	
	private String uploadTestResultToS3(String workFlowId, JarJunitResult testResult, String jarFileRemotePath, String ResultsFolderRemotePath, String unitTestClassName) throws Exception
	{
		String bucket = Config.data.defaultS3Bucket;
		
		
		String localResultsFilePath = generateFileNameForTestResult(workFlowId, jarFileRemotePath, unitTestClassName);
		createTempLocalResultsFile(localResultsFilePath, testResult);
		
		String resultsFileRemotePath = "";
		
		try
		{
			TraceWriter.trace("uploadTestClassResults: uploading file "+localResultsFilePath+" ..");
			String localJarFileName =  getFileNameFromLocalPath(localResultsFilePath);
			
			resultsFileRemotePath = ResultsFolderRemotePath + localJarFileName;
			S3FileManager.get().uploadFileToS3(bucket, localResultsFilePath, resultsFileRemotePath);
		}
		catch (IOException e)
		{
			TraceWriter.trace("uploadTestClassResults: error: can't upload results to S3!");			
			e.printStackTrace();
			throw e;
		}
		finally
		{
			TraceWriter.trace("uploadTestClassResults: finally delete temp local result");
			deleteTempLocalResultsFile(localResultsFilePath);
		}
		
		return resultsFileRemotePath;
	}
	
	private void deleteTempLocalResultsFile(String localResultsFilePath)
	{
		TraceWriter.trace("uploadTestClassResults: deleting file "+localResultsFilePath+"!");
		// attempt to clean local results file
		File f = new File(localResultsFilePath);
		f.delete();
	}
	
	private void createTempLocalResultsFile(String localResultsFilePath, JarJunitResult result) throws Exception
	{
		try
		{
			FileOutputStream os = new FileOutputStream(localResultsFilePath);
			XMLEncoder encoder = new XMLEncoder(os);
			encoder.writeObject(result);
			encoder.close();
			os.close();
		}
		catch (Exception e)
		{
			TraceWriter.trace("can't create output xml file!");
			e.printStackTrace();
			throw e;
		}
	}
	
	private String generateFileNameForTestResult(String workFlowId, String jarFileRemotePath, String unitTestClassName)
	{
		String date_str = new java.text.SimpleDateFormat("ddMMyyyy_HHmmss").format(new java.util.Date());
		
		String jar_file_name = getFileNameFromS3Path(jarFileRemotePath);
		
		String class_name_for_file = unitTestClassName.replace('/', '-');
		class_name_for_file = class_name_for_file.replace('.', '-');
		
		String file_name = TEST_OUTPUT_FILE_NAME_PREFIX+date_str+"_jar-"+jar_file_name.split("\\.")[0]+"_class-"+class_name_for_file+"_wfid-"+workFlowId+".xml";
		String resultsFilePath = LOCAL_WORK_DIR+file_name;
		
		return resultsFilePath;
	}
	
	private void downloadJarToLocalDriveIfNotThere(String bucket, String jarFileRemotePath) throws IOException
	{
		//check if jar file exists, if needed - download from S3
		String jarFileLocalPath = getJarLocalFullPath(jarFileRemotePath);
		
		createLocalFolderIfDoesNotExist(jarFileLocalPath);
		
		File f = new File(jarFileLocalPath);
		if (!f.exists())
		{
			TraceWriter.trace( "downloadJarToLocalDriveIfNotThere - downloading from: " + jarFileRemotePath );
			try
			{				
				S3FileManager.get().downloadFileFromS3(bucket, jarFileRemotePath, jarFileLocalPath);
			}
			catch (IOException e)
			{
				TraceWriter.trace("downloadJarToLocalDriveIfNotThere: error: can't download jar");
				e.printStackTrace();
				throw e;
			}
		}
		else TraceWriter.trace("downloadJarToLocalDriveIfNotThere: jar exists.");
	}
	
	private String getJarLocalFullPath(String jarFileRemotePath)
	{		
		return LOCAL_WORK_DIR + getFileNameFromS3Path(jarFileRemotePath);
	}
	
	private void createLocalFolderIfDoesNotExist(String jarFileLocalPath)
	{
		File f = new File(LOCAL_WORK_DIR);
		if (!f.exists())
		{
			TraceWriter.trace("creating directory: " + f.getAbsolutePath());
			f.mkdirs();
		}
	}
	
	private String getFileNameFromS3Path(String path)
	{
		String[] subDirs = path.split("/");
		return subDirs[subDirs.length-1]; 
	}
	
	private String getFileNameFromLocalPath(String path)
	{
		String[] subDirs = path.split(Pattern.quote(File.separator));
		return subDirs[subDirs.length-1]; 
	}
	
}